﻿using System;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Xml;
using System.Xml.Linq;
using System.Globalization;
using System.Reflection;
using System.Data.Metadata.Edm;
using System.Data.Mapping;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Text;
using System.Data.Entity.Design;


namespace Cherry.Agile.CodeGenerator
{

    public class CodeGenerationTools
    {
        private readonly DynamicTextTransformation _textTransformation;
        private readonly CSharpCodeProvider _code;
        private readonly MetadataTools _ef;

        /// <summary>
        /// Initializes a new CodeGenerationTools object with the TextTransformation (T4 generated class)
        /// that is currently running
        /// </summary>
        public CodeGenerationTools(object textTransformation)
        {
            if (textTransformation == null)
            {
                throw new ArgumentNullException("textTransformation");
            }

            _textTransformation = DynamicTextTransformation.Create(textTransformation);
            _code = new CSharpCodeProvider();
            _ef = new MetadataTools(_textTransformation);
            FullyQualifySystemTypes = false;
            CamelCaseFields = true;
        }

        /// <summary>
        /// When true, all types that are not being generated
        /// are fully qualified to keep them from conflicting with
        /// types that are being generated. Useful when you have
        /// something like a type being generated named System.
        ///
        /// Default is false.
        /// </summary>
        public bool FullyQualifySystemTypes { get; set; }

        /// <summary>
        /// When true, the field names are Camel Cased,
        /// otherwise they will preserve the case they
        /// start with.
        ///
        /// Default is true.
        /// </summary>
        public bool CamelCaseFields { get; set; }

        /// <summary>
        /// Returns the NamespaceName suggested by VS if running inside VS.  Otherwise, returns
        /// null.
        /// </summary>
        public string VsNamespaceSuggestion()
        {
            string suggestion = _textTransformation.Host.ResolveParameterValue("directiveId", "namespaceDirectiveProcessor", "namespaceHint");
            if (String.IsNullOrEmpty(suggestion))
            {
                return null;
            }

            return suggestion;
        }

        /// <summary>
        /// Returns a string that is safe for use as an identifier in C#.
        /// Keywords are escaped.
        /// </summary>
        public string Escape(string name)
        {
            if (name == null)
            {
                return null;
            }

            return _code.CreateEscapedIdentifier(name);
        }

        /// <summary>
        /// Returns the name of the TypeUsage's EdmType that is safe for
        /// use as an identifier.
        /// </summary>
        public string Escape(TypeUsage typeUsage)
        {
            if (typeUsage == null)
            {
                return null;
            }

            if (typeUsage.EdmType is ComplexType ||
                typeUsage.EdmType is EntityType)
            {
                return Escape(typeUsage.EdmType.Name);
            }
            else if (typeUsage.EdmType is PrimitiveType)
            {

                Type clrType = _ef.ClrType(typeUsage);
                string typeName = Escape(clrType);
                if (clrType.IsValueType && _ef.IsNullable(typeUsage))
                {
                    return String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName);
                }

                return typeName;
            }
            else if (typeUsage.EdmType is CollectionType)
            {
                return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", Escape(((CollectionType)typeUsage.EdmType).TypeUsage));
            }


            throw new ArgumentException("typeUsage");
        }

        /// <summary>
        /// Returns the name of the EdmMember that is safe for
        /// use as an identifier.
        /// </summary>
        public string Escape(EdmMember member)
        {
            if (member == null)
            {
                return null;
            }

            return Escape(member.Name);
        }

        /// <summary>
        /// Returns the name of the EdmType that is safe for
        /// use as an identifier.
        /// </summary>
        public string Escape(EdmType type)
        {
            if (type == null)
            {
                return null;
            }

            return Escape(type.Name);
        }

        /// <summary>
        /// Returns the name of the EdmFunction that is safe for
        /// use as an identifier.
        /// </summary>
        public string Escape(EdmFunction function)
        {
            if (function == null)
            {
                return null;
            }

            return Escape(function.Name);
        }

        /// <summary>
        /// Returns the name of the EntityContainer that is safe for
        /// use as an identifier.
        /// </summary>
        public string Escape(EntityContainer container)
        {
            if (container == null)
            {
                return null;
            }

            return Escape(container.Name);
        }

        /// <summary>
        /// Returns the name of the EntitySet that is safe for
        /// use as an identifier.
        /// </summary>
        public string Escape(EntitySet set)
        {
            if (set == null)
            {
                return null;
            }

            return Escape(set.Name);
        }

        /// <summary>
        /// Returns the name of the StructuralType that is safe for
        /// use as an identifier.
        /// </summary>
        public string Escape(StructuralType type)
        {
            if (type == null)
            {
                return null;
            }

            return Escape(type.Name);
        }

        /// <summary>
        /// Returns the NamespaceName with each segment safe to
        /// use as an identifier.
        /// </summary>
        public string EscapeNamespace(string namespaceName)
        {
            if (String.IsNullOrEmpty(namespaceName))
            {
                return namespaceName;
            }

            string[] parts = namespaceName.Split('.');
            namespaceName = String.Empty;
            foreach (string part in parts)
            {
                if (namespaceName != String.Empty)
                {
                    namespaceName += ".";
                }

                namespaceName += Escape(part);
            }

            return namespaceName;
        }

        /// <summary>
        /// Returns the name of the EdmMember formatted for
        /// use as a field identifier.
        ///
        /// This method changes behavior based on the CamelCaseFields
        /// setting.
        /// </summary>
        public string FieldName(EdmMember member)
        {
            if (member == null)
            {
                return null;
            }

            return FieldName(member.Name);
        }

        /// <summary>
        /// Returns the name of the EntitySet formatted for
        /// use as a field identifier.
        ///
        /// This method changes behavior based on the CamelCaseFields
        /// setting.
        /// </summary>
        public string FieldName(EntitySet set)
        {
            if (set == null)
            {
                return null;
            }

            return FieldName(set.Name);

        }

        private string FieldName(string name)
        {
            if (CamelCaseFields)
            {
                return "_" + CamelCase(name);
            }
            else
            {
                return "_" + name;
            }
        }

        /// <summary>
        /// Returns the name of the Type object formatted for
        /// use in source code.
        ///
        /// This method changes behavior based on the FullyQualifySystemTypes
        /// setting.
        /// </summary>
        public string Escape(Type clrType)
        {
            if (clrType == null)
            {
                return null;
            }

            string typeName;
            if (FullyQualifySystemTypes)
            {
                typeName = "global::" + clrType.FullName;
            }
            else
            {
                typeName = _code.GetTypeOutput(new CodeTypeReference(clrType));
            }
            return typeName;
        }



        /// <summary>
        /// Returns the abstract option if the entity is Abstract, otherwise returns String.Empty
        /// </summary>
        public string AbstractOption(EntityType entity)
        {
            if (entity.Abstract)
            {
                return "abstract";
            }
            return String.Empty;
        }

        /// <summary>
        /// Returns the passed in identifier with the first letter changed to lowercase
        /// </summary>
        public string CamelCase(string identifier)
        {
            if (String.IsNullOrEmpty(identifier))
            {
                return identifier;
            }

            if (identifier.Length == 1)
            {
                return identifier[0].ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
            }

            return identifier[0].ToString(CultureInfo.InvariantCulture).ToLowerInvariant() + identifier.Substring(1);
        }

        /// <summary>
        /// If the value parameter is null or empty an empty string is returned,
        /// otherwise it retuns value with a single space concatenated on the end.
        /// </summary>
        public string SpaceAfter(string value)
        {
            return StringAfter(value, " ");
        }

        /// <summary>
        /// If the value parameter is null or empty an empty string is returned,
        /// otherwise it retuns value with a single space concatenated on the end.
        /// </summary>
        public string SpaceBefore(string value)
        {
            return StringBefore(" ", value);
        }

        /// <summary>
        /// If the value parameter is null or empty an empty string is returned,
        /// otherwise it retuns value with append concatenated on the end.
        /// </summary>
        public string StringAfter(string value, string append)
        {
            if (String.IsNullOrEmpty(value))
            {
                return String.Empty;
            }

            return value + append;
        }

        /// <summary>
        /// If the value parameter is null or empty an empty string is returned,
        /// otherwise it retuns value with prepend concatenated on the front.
        /// </summary>
        public string StringBefore(string prepend, string value)
        {
            if (String.IsNullOrEmpty(value))
            {
                return String.Empty;
            }

            return prepend + value;
        }

        /// <summary>
        /// Retuns as full of a name as possible, if a namespace is provided
        /// the namespace and name are combined with a period, otherwise just
        /// the name is returned.
        /// </summary>
        public string CreateFullName(string namespaceName, string name)
        {
            if (String.IsNullOrEmpty(namespaceName))
            {
                return name;
            }

            return namespaceName + "." + name;
        }

        public string CreateLiteral(object value)
        {
            if (value == null)
            {
                return string.Empty;
            }

            Type type = value.GetType();
            if (type.IsEnum)
            {
                return type.FullName + "." + value.ToString();
            }
            if (type == typeof(Guid))
            {
                return string.Format(CultureInfo.InvariantCulture, "new Guid(\"{0}\")",
                                     ((Guid)value).ToString("D", CultureInfo.InvariantCulture));
            }
            else if (type == typeof(DateTime))
            {
                return string.Format(CultureInfo.InvariantCulture, "new DateTime({0}, DateTimeKind.Unspecified)",
                                     ((DateTime)value).Ticks);
            }
            else if (type == typeof(byte[]))
            {
                var arrayInit = string.Join(", ", ((byte[])value).Select(b => b.ToString(CultureInfo.InvariantCulture)).ToArray());
                return string.Format(CultureInfo.InvariantCulture, "new Byte[] {{{0}}}", arrayInit);
            }
            else if (type == typeof(DateTimeOffset))
            {
                var dto = (DateTimeOffset)value;
                return string.Format(CultureInfo.InvariantCulture, "new DateTimeOffset({0}, new TimeSpan({1}))",
                                     dto.Ticks, dto.Offset.Ticks);
            }

            var expression = new CodePrimitiveExpression(value);
            var writer = new StringWriter();
            CSharpCodeProvider code = new CSharpCodeProvider();
            code.GenerateCodeFromExpression(expression, writer, new CodeGeneratorOptions());
            return writer.ToString();
        }
    }

    /// <summary>
    /// Responsible for making the Entity Framework Metadata more
    /// accessible for code generation.
    /// </summary>
    public class MetadataTools
    {
        private readonly DynamicTextTransformation _textTransformation;

        /// <summary>
        /// Initializes an MetadataTools Instance  with the
        /// TextTransformation (T4 generated class) that is currently running
        /// </summary>
        public MetadataTools(object textTransformation)
        {
            if (textTransformation == null)
            {
                throw new ArgumentNullException("textTransformation");
            }

            _textTransformation = DynamicTextTransformation.Create(textTransformation);
        }

        /// <summary>
        /// If the passed in TypeUsage has an EdmType that is a PrimitiveType, this method returns
        /// the corosponding Type object, otherwise it returns the Type object for Object.
        /// </summary>
        public Type ClrType(TypeUsage typeUsage)
        {
            if (typeUsage.EdmType is PrimitiveType)
            {
                return ((PrimitiveType)typeUsage.EdmType).ClrEquivalentType;
            }

            return typeof(object);
        }

        /// <summary>
        /// True if the EdmProperty is a key of its DeclaringType, False otherwise.
        /// </summary>
        public bool IsKey(EdmProperty property)
        {
            if (property != null && property.DeclaringType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
            {
                return ((EntityType)property.DeclaringType).KeyMembers.Contains(property);
            }

            return false;
        }

        /// <summary>
        /// True if the EdmProperty TypeUsage is Nullable, False otherwise.
        /// </summary>
        public bool IsNullable(EdmProperty property)
        {
            return property != null && IsNullable(property.TypeUsage);
        }

        /// <summary>
        /// True if the TypeUsage is Nullable, False otherwise.
        /// </summary>
        public bool IsNullable(TypeUsage typeUsage)
        {
            Facet nullableFacet = null;
            if (typeUsage != null &&
                typeUsage.Facets.TryGetValue("Nullable", true, out nullableFacet))
            {
                return (bool)nullableFacet.Value;
            }

            return false;
        }

        /// <summary>
        /// If the passed in TypeUsage represents a collection this method returns final element
        /// type of the collection, otherwise it returns the value passed in.
        /// </summary>
        public TypeUsage GetElementType(TypeUsage typeUsage)
        {
            if (typeUsage == null)
            {
                return null;
            }

            if (typeUsage.EdmType is CollectionType)
            {
                return GetElementType(((CollectionType)typeUsage.EdmType).TypeUsage);
            }
            else
            {
                return typeUsage;
            }
        }

        /// <summary>
        /// Returns the NavigationProperty that is the other end of the same association set if it is
        /// available, otherwise it returns null.
        /// </summary>
        public NavigationProperty Inverse(NavigationProperty navProperty)
        {
            if (navProperty == null)
            {
                return null;
            }

            EntityType toEntity = navProperty.ToEndMember.GetEntityType();
            return toEntity.NavigationProperties
                .SingleOrDefault(n => Object.ReferenceEquals(n.RelationshipType, navProperty.RelationshipType) && !Object.ReferenceEquals(n, navProperty));
        }

        /// <summary>
        /// Given a property on the dependent end of a referential constraint, returns the corresponding property on the principal end.
        /// Requires: The association has a referential constraint, and the specified dependentProperty is one of the properties on the dependent end.
        /// </summary>
        public EdmProperty GetCorrespondingPrincipalProperty(NavigationProperty navProperty, EdmProperty dependentProperty)
        {
            if (navProperty == null)
            {
                throw new ArgumentNullException("navProperty");
            }

            if (dependentProperty == null)
            {
                throw new ArgumentNullException("dependentProperty");
            }

            ReadOnlyMetadataCollection<EdmProperty> fromProperties = GetPrincipalProperties(navProperty);
            ReadOnlyMetadataCollection<EdmProperty> toProperties = GetDependentProperties(navProperty);
            return fromProperties[toProperties.IndexOf(dependentProperty)];
        }

        /// <summary>
        /// Given a property on the principal end of a referential constraint, returns the corresponding property on the dependent end.
        /// Requires: The association has a referential constraint, and the specified principalProperty is one of the properties on the principal end.
        /// </summary>
        public EdmProperty GetCorrespondingDependentProperty(NavigationProperty navProperty, EdmProperty principalProperty)
        {
            if (navProperty == null)
            {
                throw new ArgumentNullException("navProperty");
            }

            if (principalProperty == null)
            {
                throw new ArgumentNullException("principalProperty");
            }

            ReadOnlyMetadataCollection<EdmProperty> fromProperties = GetPrincipalProperties(navProperty);
            ReadOnlyMetadataCollection<EdmProperty> toProperties = GetDependentProperties(navProperty);
            return toProperties[fromProperties.IndexOf(principalProperty)];
        }

        /// <summary>
        /// Gets the collection of properties that are on the principal end of a referential constraint for the specified navigation property.
        /// Requires: The association has a referential constraint.
        /// </summary>
        public ReadOnlyMetadataCollection<EdmProperty> GetPrincipalProperties(NavigationProperty navProperty)
        {
            if (navProperty == null)
            {
                throw new ArgumentNullException("navProperty");
            }

            return ((AssociationType)navProperty.RelationshipType).ReferentialConstraints[0].FromProperties;
        }

        /// <summary>
        /// Gets the collection of properties that are on the dependent end of a referential constraint for the specified navigation property.
        /// Requires: The association has a referential constraint.
        /// </summary>
        public ReadOnlyMetadataCollection<EdmProperty> GetDependentProperties(NavigationProperty navProperty)
        {
            if (navProperty == null)
            {
                throw new ArgumentNullException("navProperty");
            }

            return ((AssociationType)navProperty.RelationshipType).ReferentialConstraints[0].ToProperties;
        }

        /// <summary>
        /// True if the source end of the specified navigation property is the principal in an identifying relationship.
        /// or if the source end has cascade delete defined.
        /// </summary>
        public bool IsCascadeDeletePrincipal(NavigationProperty navProperty)
        {
            if (navProperty == null)
            {
                throw new ArgumentNullException("navProperty");
            }

            return IsCascadeDeletePrincipal((AssociationEndMember)navProperty.FromEndMember);
        }

        /// <summary>
        /// True if the specified association end is the principal in an identifying relationship.
        /// or if the association end has cascade delete defined.
        /// </summary>
        public bool IsCascadeDeletePrincipal(AssociationEndMember associationEnd)
        {
            if (associationEnd == null)
            {
                throw new ArgumentNullException("associationEnd");
            }

            return associationEnd.DeleteBehavior == OperationAction.Cascade || IsPrincipalEndOfIdentifyingRelationship(associationEnd);
        }

        /// <summary>
        /// True if the specified association end is the principal end in an identifying relationship.
        /// In order to be an identifying relationship, the association must have a referential constraint where all of the dependent properties are part of the dependent type's primary key.
        /// </summary>
        public bool IsPrincipalEndOfIdentifyingRelationship(AssociationEndMember associationEnd)
        {
            if (associationEnd == null)
            {
                throw new ArgumentNullException("associationEnd");
            }

            ReferentialConstraint refConstraint = ((AssociationType)associationEnd.DeclaringType).ReferentialConstraints.Where(rc => rc.FromRole == associationEnd).SingleOrDefault();
            if (refConstraint != null)
            {
                EntityType entity = refConstraint.ToRole.GetEntityType();
                return !refConstraint.ToProperties.Where(tp => !entity.KeyMembers.Contains(tp)).Any();
            }
            return false;
        }

        /// <summary>
        /// True if the specified association type is an identifying relationship.
        /// In order to be an identifying relationship, the association must have a referential constraint where all of the dependent properties are part of the dependent type's primary key.
        /// </summary>
        public bool IsIdentifyingRelationship(AssociationType association)
        {
            if (association == null)
            {
                throw new ArgumentNullException("association");
            }

            return IsPrincipalEndOfIdentifyingRelationship(association.AssociationEndMembers[0]) || IsPrincipalEndOfIdentifyingRelationship(association.AssociationEndMembers[1]);
        }

        /// <summary>
        /// requires: firstType is not null
        /// effects: if secondType is among the base types of the firstType, return true,
        /// otherwise returns false.
        /// when firstType is same as the secondType, return false.
        /// </summary>
        public bool IsSubtypeOf(EdmType firstType, EdmType secondType)
        {
            if (secondType == null)
            {
                return false;
            }

            // walk up firstType hierarchy list
            for (EdmType t = firstType.BaseType; t != null; t = t.BaseType)
            {
                if (t == secondType)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Returns the subtype of the EntityType in the current itemCollection
        /// </summary>
        public IEnumerable<EntityType> GetSubtypesOf(EntityType type, ItemCollection itemCollection, bool includeAbstractTypes)
        {
            if (type != null)
            {
                IEnumerable<EntityType> typesInCollection = itemCollection.GetItems<EntityType>();
                foreach (EntityType typeInCollection in typesInCollection)
                {
                    if (type.Equals(typeInCollection) == false && this.IsSubtypeOf(typeInCollection, type))
                    {
                        if (includeAbstractTypes || !typeInCollection.Abstract)
                        {
                            yield return typeInCollection;
                        }
                    }
                }
            }
        }

        public static bool TryGetStringMetadataPropertySetting(MetadataItem item, string propertyName, out string value)
        {
            value = null;
            MetadataProperty property = item.MetadataProperties.FirstOrDefault(p => p.Name == propertyName);
            if (property != null)
            {
                value = (string)property.Value;
            }
            return value != null;
        }
    }

    /// <summary>
    /// Responsible for loading an EdmItemCollection from a .edmx file or .csdl files
    /// </summary>
    public class MetadataLoader
    {
        private readonly DynamicTextTransformation _textTransformation;

        /// <summary>
        /// Initializes an MetadataLoader Instance  with the
        /// TextTransformation (T4 generated class) that is currently running
        /// </summary>
        public MetadataLoader(object textTransformation)
        {
            if (textTransformation == null)
            {
                throw new ArgumentNullException("textTransformation");
            }

            _textTransformation = DynamicTextTransformation.Create(textTransformation);
        }

        /// <summary>
        /// Load the metadata for Edm, Store, and Mapping collections and register them
        /// with a new MetadataWorkspace, returns false if any of the parts can't be
        /// created, some of the ItemCollections may be registered and usable even if false is
        /// returned
        /// </summary>
        public bool TryLoadAllMetadata(string inputFile, out MetadataWorkspace metadataWorkspace)
        {
            metadataWorkspace = new MetadataWorkspace();

            EdmItemCollection edmItemCollection = CreateEdmItemCollection(inputFile);
            metadataWorkspace.RegisterItemCollection(edmItemCollection);


            StoreItemCollection storeItemCollection = null;
            if (TryCreateStoreItemCollection(inputFile, out storeItemCollection))
            {
                StorageMappingItemCollection storageMappingItemCollection = null;
                if (TryCreateStorageMappingItemCollection(inputFile, edmItemCollection, storeItemCollection, out storageMappingItemCollection))
                {
                    metadataWorkspace.RegisterItemCollection(storeItemCollection);
                    metadataWorkspace.RegisterItemCollection(storageMappingItemCollection);
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Create an EdmItemCollection loaded with the metadata provided
        /// </summary>
        public EdmItemCollection CreateEdmItemCollection(string sourcePath, params string[] referenceSchemas)
        {
            EdmItemCollection edmItemCollection;
            if (TryCreateEdmItemCollection(sourcePath, referenceSchemas, out edmItemCollection))
            {
                return edmItemCollection;
            }

            return new EdmItemCollection();
        }

        /// <summary>
        /// Attempts to create a EdmItemCollection from the specified metadata file
        /// </summary>
        public bool TryCreateEdmItemCollection(string sourcePath, out EdmItemCollection edmItemCollection)
        {
            return TryCreateEdmItemCollection(sourcePath, null, out edmItemCollection);
        }

        /// <summary>
        /// Attempts to create a EdmItemCollection from the specified metadata file
        /// </summary>
        public bool TryCreateEdmItemCollection(string sourcePath, string[] referenceSchemas, out EdmItemCollection edmItemCollection)
        {
            edmItemCollection = null;

            if (String.IsNullOrEmpty(sourcePath))
            {
                throw new ArgumentException("sourcePath");
            }

            if (referenceSchemas == null)
            {
                referenceSchemas = new string[0];
            }

            ItemCollection itemCollection = null;
            sourcePath = _textTransformation.Host.ResolvePath(sourcePath);
            EdmItemCollectionBuilder collectionBuilder = new EdmItemCollectionBuilder(_textTransformation, referenceSchemas.Select(s => _textTransformation.Host.ResolvePath(s)).Where(s => s != sourcePath));
            if (collectionBuilder.TryCreateItemCollection(sourcePath, out itemCollection))
            {
                edmItemCollection = (EdmItemCollection)itemCollection;
            }

            return edmItemCollection != null;
        }

        /// <summary>
        /// Attempts to create a StoreItemCollection from the specified metadata file
        /// </summary>
        public bool TryCreateStoreItemCollection(string sourcePath, out StoreItemCollection storeItemCollection)
        {
            storeItemCollection = null;

            if (String.IsNullOrEmpty(sourcePath))
            {
                throw new ArgumentException("sourcePath");
            }

            ItemCollection itemCollection = null;
            StoreItemCollectionBuilder collectionBuilder = new StoreItemCollectionBuilder(_textTransformation);
            if (collectionBuilder.TryCreateItemCollection(_textTransformation.Host.ResolvePath(sourcePath), out itemCollection))
            {
                storeItemCollection = (StoreItemCollection)itemCollection;
            }
            return storeItemCollection != null;
        }

        /// <summary>
        /// Attempts to create a StorageMappingItemCollection from the specified metadata file, EdmItemCollection, and StoreItemCollection
        /// </summary>
        public bool TryCreateStorageMappingItemCollection(string sourcePath, EdmItemCollection edmItemCollection, StoreItemCollection storeItemCollection, out StorageMappingItemCollection storageMappingItemCollection)
        {
            storageMappingItemCollection = null;

            if (String.IsNullOrEmpty(sourcePath))
            {
                throw new ArgumentException("sourcePath");
            }

            if (edmItemCollection == null)
            {
                throw new ArgumentNullException("edmItemCollection");
            }

            if (storeItemCollection == null)
            {
                throw new ArgumentNullException("storeItemCollection");
            }

            ItemCollection itemCollection = null;
            StorageMappingItemCollectionBuilder collectionBuilder = new StorageMappingItemCollectionBuilder(_textTransformation, edmItemCollection, storeItemCollection);
            if (collectionBuilder.TryCreateItemCollection(_textTransformation.Host.ResolvePath(sourcePath), out itemCollection))
            {
                storageMappingItemCollection = (StorageMappingItemCollection)itemCollection;
            }
            return storageMappingItemCollection != null;
        }

        /// <summary>
        /// Gets the Model Namespace from the provided schema file.
        /// </summary>
        public string GetModelNamespace(string sourcePath)
        {
            if (String.IsNullOrEmpty(sourcePath))
            {
                throw new ArgumentException("sourcePath");
            }

            if (sourcePath == "$edmxInputFile$")
            {
                _textTransformation.Errors.Add(new CompilerError(_textTransformation.Host.TemplateFile ?? "Currently Running Template", 0, 0, "",
                    "Please overwrite the replacement token '$edmxInputFile$' with the actual name of the .edmx file you would like to generate from."));
                return String.Empty;
            }

            EdmItemCollectionBuilder builder = new EdmItemCollectionBuilder(_textTransformation);
            XElement model;
            if (builder.TryLoadRootElement(_textTransformation.Host.ResolvePath(sourcePath), out model))
            {
                XAttribute attribute = model.Attribute("Namespace");
                if (attribute != null)
                {
                    return attribute.Value;
                }
            }

            return String.Empty;
        }

        /// <summary>
        /// base class for ItemCollectionBuilder classes that
        /// load the specific types of metadata
        /// </summary>
        private abstract class ItemCollectionBuilder
        {
            private readonly DynamicTextTransformation _textTransformation;
            private readonly string _fileExtension;
            private readonly string _namespaceV1;
            private readonly string _namespaceV2;
            private readonly string _namespaceV3;
            private readonly string _edmxSectionName;
            private readonly string _rootElementName;

            /// <summary>
            /// FileExtension for individual (non-edmx) metadata file for this
            /// specific ItemCollection type
            /// </summary>
            public string FileExtension
            {
                get { return _fileExtension; }
            }

            /// <summary>
            /// EF Version 1 XmlNamespace name
            /// </summary>
            public string NamespaceV1
            {
                get { return _namespaceV1; }
            }

            /// <summary>
            /// EF Version 2 XmlNamespace name
            /// </summary>
            public string NamespaceV2
            {
                get { return _namespaceV2; }
            }

            /// <summary>
            /// EF Version 2 XmlNamespace name
            /// </summary>
            public string NamespaceV3
            {
                get { return _namespaceV3; }
            }


            /// <summary>
            /// The name of the XmlElement in the .edmx <Runtime> element
            /// to find this ItemCollection's metadata
            /// </summary>
            public string EdmxSectionName
            {
                get { return _edmxSectionName; }
            }

            /// <summary>
            /// The name of the root element of this ItemCollection's metadata
            /// </summary>
            public string RootElementName
            {
                get { return _rootElementName; }
            }

            /// <summary>
            /// Method to build the appropriate ItemCollection
            /// </summary>
            protected abstract ItemCollection CreateItemCollection(IEnumerable<XmlReader> readers, out IList<EdmSchemaError> errors);

            /// <summary>
            /// Ctor to setup the ItemCollectionBuilder members
            /// </summary>
            protected ItemCollectionBuilder(DynamicTextTransformation textTransformation, string fileExtension, string namespaceV1, string namespaceV2, string namespaceV3, string edmxSectionName, string rootElementName)
            {
                _textTransformation = textTransformation;
                _fileExtension = fileExtension;
                _namespaceV1 = namespaceV1;
                _namespaceV2 = namespaceV2;
                _namespaceV3 = namespaceV3;
                _edmxSectionName = edmxSectionName;
                _rootElementName = rootElementName;
            }

            /// <summary>
            /// Try to create an ItemCollection loaded with the metadata provided
            /// </summary>
            public bool TryCreateItemCollection(string sourcePath, out ItemCollection itemCollection)
            {
                itemCollection = null;

                if (String.IsNullOrEmpty(sourcePath))
                {
                    throw new ArgumentException("sourcePath");
                }

                if (sourcePath == "$edmxInputFile$")
                {
                    _textTransformation.Errors.Add(new CompilerError(_textTransformation.Host.TemplateFile ?? "Currently Running Template", 0, 0, "",
                        "Please overwrite the replacement token '$edmxInputFile$' with the actual name of the .edmx file you would like to generate from."));
                    return false;
                }

                XElement schemaElement = null;
                if (TryLoadRootElement(sourcePath, out schemaElement))
                {
                    List<XmlReader> readers = new List<XmlReader>();
                    try
                    {
                        readers.Add(schemaElement.CreateReader());
                        IList<EdmSchemaError> errors = null;

                        ItemCollection tempItemCollection = CreateItemCollection(readers, out errors);
                        if (ProcessErrors(errors, sourcePath))
                        {
                            return false;
                        }

                        itemCollection = tempItemCollection;
                        return true;
                    }
                    finally
                    {
                        foreach (XmlReader reader in readers)
                        {
                            ((IDisposable)reader).Dispose();
                        }
                    }
                }

                return false;
            }

            /// <summary>
            /// Tries to load the root element from the metadata file provided
            /// </summary>
            public bool TryLoadRootElement(string sourcePath, out XElement schemaElement)
            {
                schemaElement = null;
                string extension = Path.GetExtension(sourcePath);
                if (extension.Equals(".edmx", StringComparison.InvariantCultureIgnoreCase))
                {
                    return TryLoadRootElementFromEdmx(sourcePath, out schemaElement);
                }
                else if (extension.Equals(FileExtension, StringComparison.InvariantCultureIgnoreCase))
                {
                    // load from single metadata file (.csdl, .ssdl, or .msl)
                    schemaElement = XElement.Load(sourcePath, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
                    return true;
                }

                return false;
            }

            /// <summary>
            /// Trys to load the root element from the edmxDocument provided
            /// </summary>
            private static bool TryLoadRootElementFromEdmx(XElement edmxDocument, string edmxNamespace, string sectionNamespace, string sectionName, string rootElementName, out XElement rootElement)
            {
                rootElement = null;

                XNamespace edmxNs = edmxNamespace;
                XNamespace sectionNs = sectionNamespace;

                XElement runtime = edmxDocument.Element(edmxNs + "Runtime");
                if (runtime == null)
                    return false;

                XElement section = runtime.Element(edmxNs + sectionName);
                if (section == null)
                    return false;

                rootElement = section.Element(sectionNs + rootElementName);
                return rootElement != null;
            }

            /// <summary>
            /// Trys to load the root element from the .edmx metadata file provided
            /// </summary>
            private bool TryLoadRootElementFromEdmx(string edmxPath, out XElement rootElement)
            {
                rootElement = null;

                XElement element = XElement.Load(edmxPath, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);

                return TryLoadRootElementFromEdmx(element, MetadataConstants.EDMX_NAMESPACE_V2, NamespaceV2, EdmxSectionName, RootElementName, out rootElement)
                    || TryLoadRootElementFromEdmx(element, MetadataConstants.EDMX_NAMESPACE_V1, NamespaceV1, EdmxSectionName, RootElementName, out rootElement)
                    || TryLoadRootElementFromEdmx(element, MetadataConstants.EDMX_NAMESPACE_V3, NamespaceV3, EdmxSectionName, RootElementName, out rootElement);
            }

            /// <summary>
            /// Takes an Enumerable of EdmSchemaErrors, and adds them
            /// to the errors collection of the template class
            /// </summary>
            private bool ProcessErrors(IEnumerable<EdmSchemaError> errors, string sourceFilePath)
            {
                bool foundErrors = false;
                foreach (EdmSchemaError error in errors)
                {
                    CompilerError newError = new CompilerError(error.SchemaLocation, error.Line, error.Column,
                                                     error.ErrorCode.ToString(CultureInfo.InvariantCulture),
                                                     error.Message);
                    newError.IsWarning = error.Severity == EdmSchemaErrorSeverity.Warning;
                    foundErrors |= error.Severity == EdmSchemaErrorSeverity.Error;
                    if (error.SchemaLocation == null)
                    {
                        newError.FileName = sourceFilePath;
                    }
                    _textTransformation.Errors.Add(newError);
                }

                return foundErrors;
            }
        }

        /// <summary>
        /// Builder class for creating a StorageMappingItemCollection
        /// </summary>
        private class StorageMappingItemCollectionBuilder : ItemCollectionBuilder
        {
            private readonly EdmItemCollection _edmItemCollection;
            private readonly StoreItemCollection _storeItemCollection;

            public StorageMappingItemCollectionBuilder(DynamicTextTransformation textTransformation, EdmItemCollection edmItemCollection, StoreItemCollection storeItemCollection)
                : base(textTransformation, MetadataConstants.MSL_EXTENSION, MetadataConstants.MSL_NAMESPACE_V1, MetadataConstants.MSL_NAMESPACE_V2, MetadataConstants.MSL_NAMESPACE_V3, MetadataConstants.MSL_EDMX_SECTION_NAME, MetadataConstants.MSL_ROOT_ELEMENT_NAME)
            {
                _edmItemCollection = edmItemCollection;
                _storeItemCollection = storeItemCollection;
            }

            protected override ItemCollection CreateItemCollection(IEnumerable<XmlReader> readers, out IList<EdmSchemaError> errors)
            {
                return MetadataItemCollectionFactory.CreateStorageMappingItemCollection(_edmItemCollection, _storeItemCollection, readers, out errors);
            }
        }

        /// <summary>
        /// Builder class for creating a StoreItemCollection
        /// </summary>
        private class StoreItemCollectionBuilder : ItemCollectionBuilder
        {
            public StoreItemCollectionBuilder(DynamicTextTransformation textTransformation)
                : base(textTransformation, MetadataConstants.SSDL_EXTENSION, MetadataConstants.SSDL_NAMESPACE_V1, MetadataConstants.SSDL_NAMESPACE_V2, MetadataConstants.SSDL_NAMESPACE_V3, MetadataConstants.SSDL_EDMX_SECTION_NAME, MetadataConstants.SSDL_ROOT_ELEMENT_NAME)
            {
            }

            protected override ItemCollection CreateItemCollection(IEnumerable<XmlReader> readers, out IList<EdmSchemaError> errors)
            {
                return MetadataItemCollectionFactory.CreateStoreItemCollection(readers, out errors);
            }
        }

        /// <summary>
        /// Builder class for creating a EdmItemCollection
        /// </summary>
        private class EdmItemCollectionBuilder : ItemCollectionBuilder
        {
            private List<string> _referenceSchemas = new List<string>();

            public EdmItemCollectionBuilder(DynamicTextTransformation textTransformation)
                : base(textTransformation, MetadataConstants.CSDL_EXTENSION, MetadataConstants.CSDL_NAMESPACE_V1, MetadataConstants.CSDL_NAMESPACE_V2, MetadataConstants.CSDL_NAMESPACE_V3, MetadataConstants.CSDL_EDMX_SECTION_NAME, MetadataConstants.CSDL_ROOT_ELEMENT_NAME)
            {
            }

            public EdmItemCollectionBuilder(DynamicTextTransformation textTransformation, IEnumerable<string> referenceSchemas)
                : this(textTransformation)
            {
                _referenceSchemas.AddRange(referenceSchemas);
            }

            protected override ItemCollection CreateItemCollection(IEnumerable<XmlReader> readers, out IList<EdmSchemaError> errors)
            {
                List<XmlReader> ownedReaders = new List<XmlReader>();
                List<XmlReader> allReaders = new List<XmlReader>();
                try
                {
                    allReaders.AddRange(readers);
                    foreach (string path in _referenceSchemas.Distinct())
                    {
                        XElement reference;
                        if (TryLoadRootElement(path, out reference))
                        {
                            XmlReader reader = reference.CreateReader();
                            allReaders.Add(reader);
                            ownedReaders.Add(reader);
                        }
                    }

                    return MetadataItemCollectionFactory.CreateEdmItemCollection(allReaders, out errors);
                }
                finally
                {
                    foreach (XmlReader reader in ownedReaders)
                    {
                        ((IDisposable)reader).Dispose();
                    }
                }
            }
        }
    }

    /// <summary>
    /// Responsible for encapsulating the retrieval and translation of the CodeGeneration
    /// annotations in the EntityFramework Metadata to a form that is useful in code generation.
    /// </summary>
    public static class Accessibility
    {
        private const string GETTER_ACCESS = "http://schemas.microsoft.com/ado/2006/04/codegeneration:GetterAccess";
        private const string SETTER_ACCESS = "http://schemas.microsoft.com/ado/2006/04/codegeneration:SetterAccess";
        private const string TYPE_ACCESS = "http://schemas.microsoft.com/ado/2006/04/codegeneration:TypeAccess";
        private const string METHOD_ACCESS = "http://schemas.microsoft.com/ado/2006/04/codegeneration:MethodAccess";
        private const string ACCESS_PROTECTED = "Protected";
        private const string ACCESS_INTERNAL = "Internal";
        private const string ACCESS_PRIVATE = "Private";
        private static readonly Dictionary<string, int> AccessibilityRankIdLookup = new Dictionary<string, int>
        {
            { "private", 1},
            { "internal", 2},
            { "protected", 3},
            { "public", 4},
        };

        /// <summary>
        /// Gets the accessibility that should be applied to a type being generated from the provided GlobalItem.
        ///
        /// defaults to public if no annotation is found.
        /// </summary>
        public static string ForType(GlobalItem item)
        {
            if (item == null)
            {
                return null;
            }

            return GetAccessibility(item, TYPE_ACCESS);
        }

        /// <summary>
        /// Gets the accessibility that should be applied at the property level for a property being
        /// generated from the provided EdmMember.
        ///
        /// defaults to public if no annotation is found.
        /// </summary>
        public static string ForProperty(EdmMember member)
        {
            if (member == null)
            {
                return null;
            }

            string getterAccess, setterAccess, propertyAccess;
            CalculatePropertyAccessibility(member, out propertyAccess, out getterAccess, out setterAccess);
            return propertyAccess;
        }

        /// <summary>
        /// Gets the accessibility that should be applied at the property level for a Read-Only property being
        /// generated from the provided EdmMember.
        ///
        /// defaults to public if no annotation is found.
        /// </summary>
        public static string ForReadOnlyProperty(EdmMember member)
        {
            if (member == null)
            {
                return null;
            }

            return GetAccessibility(member, GETTER_ACCESS);
        }

        /// <summary>
        /// Gets the accessibility that should be applied at the property level for a property being
        /// generated from the provided EntitySet.
        ///
        /// defaults to public if no annotation is found.
        /// </summary>
        public static string ForReadOnlyProperty(EntitySet set)
        {
            if (set == null)
            {
                return null;
            }

            return GetAccessibility(set, GETTER_ACCESS);
        }

        /// <summary>
        /// Gets the accessibility that should be applied at the property level for a Write-Only property being
        /// generated from the provided EdmMember.
        ///
        /// defaults to public if no annotation is found.
        /// </summary>
        public static string ForWriteOnlyProperty(EdmMember member)
        {
            if (member == null)
            {
                return null;
            }

            return GetAccessibility(member, SETTER_ACCESS);
        }


        /// <summary>
        /// Gets the accessibility that should be applied at the get level for a property being
        /// generated from the provided EdmMember.
        ///
        /// defaults to empty if no annotation is found or the accessibility is the same as the property level.
        /// </summary>
        public static string ForGetter(EdmMember member)
        {
            if (member == null)
            {
                return null;
            }

            string getterAccess, setterAccess, propertyAccess;
            CalculatePropertyAccessibility(member, out propertyAccess, out getterAccess, out setterAccess);
            return getterAccess;
        }

        /// <summary>
        /// Gets the accessibility that should be applied at the set level for a property being
        /// generated from the provided EdmMember.
        ///
        /// defaults to empty if no annotation is found or the accessibility is the same as the property level.
        /// </summary>
        public static string ForSetter(EdmMember member)
        {
            if (member == null)
            {
                return null;
            }

            string getterAccess, setterAccess, propertyAccess;
            CalculatePropertyAccessibility(member, out propertyAccess, out getterAccess, out setterAccess);
            return setterAccess;
        }

        /// <summary>
        /// Gets the accessibility that should be applied to a method being generated from the provided EdmFunction.
        ///
        /// defaults to public if no annotation is found.
        /// </summary>
        public static string ForMethod(EdmFunction function)
        {
            if (function == null)
            {
                return null;
            }

            return GetAccessibility(function, METHOD_ACCESS);
        }

        private static void CalculatePropertyAccessibility(MetadataItem item,
            out string propertyAccessibility,
            out string getterAccessibility,
            out string setterAccessibility)
        {
            getterAccessibility = GetAccessibility(item, GETTER_ACCESS);
            int getterRank = AccessibilityRankIdLookup[getterAccessibility];

            setterAccessibility = GetAccessibility(item, SETTER_ACCESS);
            int setterRank = AccessibilityRankIdLookup[setterAccessibility];

            int propertyRank = Math.Max(getterRank, setterRank);
            if (setterRank == propertyRank)
            {
                setterAccessibility = String.Empty;
            }

            if (getterRank == propertyRank)
            {
                getterAccessibility = String.Empty;
            }

            propertyAccessibility = AccessibilityRankIdLookup.Where(v => v.Value == propertyRank).Select(v => v.Key).Single();
        }

        private static string GetAccessibility(MetadataItem item, string name)
        {
            string accessibility;
            if (MetadataTools.TryGetStringMetadataPropertySetting(item, name, out accessibility))
            {
                return TranslateUserAccessibilityToCSharpAccessibility(accessibility);
            }

            return "public";
        }

        private static string TranslateUserAccessibilityToCSharpAccessibility(string userAccessibility)
        {
            if (userAccessibility == ACCESS_PROTECTED)
            {
                return "protected";
            }
            else if (userAccessibility == ACCESS_INTERNAL)
            {
                return "internal";
            }
            else if (userAccessibility == ACCESS_PRIVATE)
            {
                return "private";
            }
            else
            {
                // default to public
                return "public";
            }
        }

    }

    /// <summary>
    /// Responsible for creating source code regions in code when the loop inside
    /// actually produces something.
    /// </summary>
    public class CodeRegion
    {
        private const int STANDARD_INDENT_LENGTH = 4;

        private readonly DynamicTextTransformation _textTransformation;
        private int _beforeRegionLength;
        private int _emptyRegionLength;
        private int _regionIndentLevel = -1;

        /// <summary>
        /// Initializes an CodeRegion instance with the
        /// TextTransformation (T4 generated class) that is currently running
        /// </summary>
        public CodeRegion(object textTransformation)
        {
            if (textTransformation == null)
            {
                throw new ArgumentNullException("textTransformation");
            }

            _textTransformation = DynamicTextTransformation.Create(textTransformation);
        }

        /// <summary>
        /// Initializes an CodeRegion instance with the
        /// TextTransformation (T4 generated class) that is currently running,
        /// and the indent level to start the first region at.
        /// </summary>
        public CodeRegion(object textTransformation, int firstIndentLevel)
            : this(textTransformation)
        {
            if (firstIndentLevel < 0)
            {
                throw new ArgumentException("firstIndentLevel");
            }

            _regionIndentLevel = firstIndentLevel - 1;
        }

        /// <summary>
        /// Starts the begining of a region
        /// </summary>
        public void Begin(string regionName)
        {
            if (regionName == null)
            {
                throw new ArgumentNullException("regionName");
            }

            Begin(regionName, 1);
        }

        /// <summary>
        /// Start the begining of a region, indented
        /// the numbers of levels specified
        /// </summary>
        public void Begin(string regionName, int levelsToIncreaseIndent)
        {
            if (regionName == null)
            {
                throw new ArgumentNullException("regionName");
            }

            _beforeRegionLength = _textTransformation.GenerationEnvironment.Length;
            _regionIndentLevel += levelsToIncreaseIndent;
            _textTransformation.Write(GetIndent(_regionIndentLevel));
            _textTransformation.WriteLine("#region " + regionName);
            _emptyRegionLength = _textTransformation.GenerationEnvironment.Length;
        }

        /// <summary>
        /// Ends a region, or totaly removes it if nothing
        /// was generted since the begining of the region.
        /// </summary>
        public void End()
        {
            End(1);
        }

        /// <summary>
        /// Ends a region, or totaly removes it if nothing
        /// was generted since the begining of the region, also outdents
        /// the number of levels specified.
        /// </summary>
        public void End(int levelsToDecrease)
        {
            int indentLevel = _regionIndentLevel;
            _regionIndentLevel -= levelsToDecrease;

            if (_emptyRegionLength == _textTransformation.GenerationEnvironment.Length)
                _textTransformation.GenerationEnvironment.Length = _beforeRegionLength;
            else
            {
                _textTransformation.WriteLine(String.Empty);
                _textTransformation.Write(GetIndent(indentLevel));
                _textTransformation.WriteLine("#endregion");
            }
        }

        /// <summary>
        /// Gets the current indent level that the next end region statement will be written
        /// at
        /// </summary>
        public int CurrentIndentLevel { get { return _regionIndentLevel; } }

        /// <summary>
        /// Get a string of spaces equivelent to the number of indents
        /// desired.
        /// </summary>
        public static string GetIndent(int indentLevel)
        {
            if (indentLevel < 0)
            {
                throw new ArgumentException("indentLevel");
            }

            return String.Empty.PadLeft(indentLevel * STANDARD_INDENT_LENGTH);
        }
    }




    /// <summary>
    /// Responsible for collecting together the actual method parameters
    /// and the parameters that need to be sent to the Execute method.
    /// </summary>
    public class FunctionImportParameter
    {
        public FunctionParameter Source { get; set; }
        public string RawFunctionParameterName { get; set; }
        public string FunctionParameterName { get; set; }
        public string FunctionParameterType { get; set; }
        public string LocalVariableName { get; set; }
        public string RawClrTypeName { get; set; }
        public string ExecuteParameterName { get; set; }
        public string EsqlParameterName { get; set; }
        public bool NeedsLocalVariable { get; set; }
        public bool IsNullableOfT { get; set; }


        /// <summary>
        /// Creates a set of FunctionImportParameter objects from the parameters passed in.
        /// </summary>
        public static IEnumerable<FunctionImportParameter> Create(IEnumerable<FunctionParameter> parameters, CodeGenerationTools code, MetadataTools ef)
        {
            if (parameters == null)
            {
                throw new ArgumentNullException("parameters");
            }

            if (code == null)
            {
                throw new ArgumentNullException("code");
            }

            if (ef == null)
            {
                throw new ArgumentNullException("ef");
            }

            UniqueIdentifierService unique = new UniqueIdentifierService();
            List<FunctionImportParameter> importParameters = new List<FunctionImportParameter>();
            foreach (FunctionParameter parameter in parameters)
            {
                FunctionImportParameter importParameter = new FunctionImportParameter();
                importParameter.Source = parameter;
                importParameter.RawFunctionParameterName = unique.AdjustIdentifier(code.CamelCase(parameter.Name));
                importParameter.FunctionParameterName = code.Escape(importParameter.RawFunctionParameterName);
                if (parameter.Mode == ParameterMode.In)
                {
                    importParameter.NeedsLocalVariable = true;
                    importParameter.FunctionParameterType = code.Escape(parameter.TypeUsage);
                    importParameter.EsqlParameterName = parameter.Name;
                    Type clrType = ef.ClrType(parameter.TypeUsage);
                    importParameter.RawClrTypeName = code.Escape(clrType);
                    importParameter.IsNullableOfT = clrType.IsValueType;
                }
                else
                {
                    importParameter.NeedsLocalVariable = false;
                    importParameter.FunctionParameterType = "ObjectParameter";
                    importParameter.ExecuteParameterName = importParameter.FunctionParameterName;
                }
                importParameters.Add(importParameter);
            }

            // we save the local parameter uniquification for a second pass to make the visible parameters
            // as pretty and sensible as possible
            for (int i = 0; i < importParameters.Count; i++)
            {
                FunctionImportParameter importParameter = importParameters[i];
                if (importParameter.NeedsLocalVariable)
                {
                    importParameter.LocalVariableName = unique.AdjustIdentifier(importParameter.RawFunctionParameterName + "Parameter");
                    importParameter.ExecuteParameterName = importParameter.LocalVariableName;
                }
            }

            return importParameters;
        }

        //
        // Class to create unique variables within the same scope
        //
        private sealed class UniqueIdentifierService
        {
            private readonly HashSet<string> _knownIdentifiers;

            public UniqueIdentifierService()
            {
                _knownIdentifiers = new HashSet<string>(StringComparer.Ordinal);
            }

            /// <summary>
            /// Given an identifier, makes it unique within the scope by adding
            /// a suffix (1, 2, 3, ...), and returns the adjusted identifier.
            /// </summary>
            public string AdjustIdentifier(string identifier)
            {
                // find a unique name by adding suffix as necessary
                int numberOfConflicts = 0;
                string adjustedIdentifier = identifier;

                while (!_knownIdentifiers.Add(adjustedIdentifier))
                {
                    ++numberOfConflicts;
                    adjustedIdentifier = identifier + numberOfConflicts.ToString(CultureInfo.InvariantCulture);
                }

                return adjustedIdentifier;
            }
        }

        private string FunctionImportTypeName(FunctionParameter parameter)
        {
            return parameter.Mode == ParameterMode.In ? parameter.TypeUsage.EdmType.Name : "ObjectParameter";
        }
    }

    /// <summary>
    /// Responsible for marking the various sections of the generation,
    /// so they can be split up into separate files
    /// </summary>
    public class EntityFrameworkTemplateFileManager
    {
        #region events
        public static event EventHandler<EventArgs> EntitiesCountChanged;
        public static event EventHandler<EventArgs> ProcessEntity;
        public static event EventHandler<EventArgs> BuildEntityFinished;

        public void SetEntitiesCount(int count)
        {
            EntitiesCount = count;
            if (EntitiesCountChanged != null)
                EntitiesCountChanged(this, EventArgs.Empty);
        }

        public void BuildEntityProcessCountChanged(int processedEntitiesCount)
        {
            ProcessEntitiesCount = processedEntitiesCount;
            if (ProcessEntity != null)
                ProcessEntity(this, EventArgs.Empty);
        }

        public void BuildEntityComplete()
        {
            if (BuildEntityFinished != null)
                BuildEntityFinished(this, EventArgs.Empty);
        }
        #endregion

        #region public property
        public static int EntitiesCount { get; private set; }
        public static int ProcessEntitiesCount { get; private set; }
        #endregion

        public string OutputPath;
        public static EntityFrameworkTemplateHost TemplateHost;
        /// <summary>
        /// Creates the VsEntityFrameworkTemplateFileManager if VS is detected, otherwise
        /// creates the file system version.
        /// </summary>
        public static EntityFrameworkTemplateFileManager Create(dynamic textTransformation,string outputPath)
        {
            
            DynamicTextTransformation transformation = DynamicTextTransformation.Create(textTransformation);
            IDynamicHost host = transformation.Host;

#if !PREPROCESSED_TEMPLATE
            if (host.AsIServiceProvider() != null)
            {
                var v = new VsEntityFrameworkTemplateFileManager(transformation);
                v.OutputPath = outputPath;
                return v;   
            }
#endif
            var et = new EntityFrameworkTemplateFileManager(transformation);
            et.OutputPath = outputPath;
            return et;
        }

        private sealed class Block
        {
            public String Name;
            public int Start, Length;
        }

        private readonly List<Block> files = new List<Block>();
        private readonly Block footer = new Block();
        private readonly Block header = new Block();
        private readonly DynamicTextTransformation _textTransformation;

        // reference to the GenerationEnvironment StringBuilder on the
        // TextTransformation object
        private readonly StringBuilder _generationEnvironment;

        private Block currentBlock;

        /// <summary>
        /// Initializes an EntityFrameworkTemplateFileManager Instance  with the
        /// TextTransformation (T4 generated class) that is currently running
        /// </summary>
        private EntityFrameworkTemplateFileManager(dynamic textTransformation)
        {
            EntitiesCount = 0;
            ProcessEntitiesCount = 0;

            if (textTransformation == null)
            {
                throw new ArgumentNullException("textTransformation");
            }

            _textTransformation = DynamicTextTransformation.Create(textTransformation);
            TemplateHost = (_textTransformation.Host as DynamicHost).TemplateHost;
            _generationEnvironment = _textTransformation.GenerationEnvironment;
        }

        /// <summary>
        /// Marks the end of the last file if there was one, and starts a new
        /// and marks this point in generation as a new file.
        /// </summary>
        public void StartNewFile(string name)
        {
            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            CurrentBlock = new Block { Name = name };
        }

        public void StartFooter()
        {
            CurrentBlock = footer;
        }

        public void StartHeader()
        {
            CurrentBlock = header;
        }

        public void EndBlock()
        {
            if (CurrentBlock == null)
            {
                return;
            }

            CurrentBlock.Length = _generationEnvironment.Length - CurrentBlock.Start;

            if (CurrentBlock != header && CurrentBlock != footer)
            {
                files.Add(CurrentBlock);
            }

            currentBlock = null;
        }

        private string GetOuptPath()
        {
            //_textTransformation.Host.TemplateFile
            return OutputPath;
        }

        /// <summary>
        /// Produce the template output files.
        /// </summary>
        public virtual IEnumerable<string> Process(bool split = true)
        {
            var generatedFileNames = new List<string>();

            if (split)
            {
                EndBlock();

                var headerText = _generationEnvironment.ToString(header.Start, header.Length);
                var footerText = _generationEnvironment.ToString(footer.Start, footer.Length);
                var outputPath =  GetOuptPath();

                files.Reverse();

                foreach (var block in files)
                {
                    var fileName = Path.Combine(outputPath, block.Name);
                    var content = headerText + _generationEnvironment.ToString(block.Start, block.Length) + footerText;

                    generatedFileNames.Add(fileName);

                    CreateFile(block.Name, content);

                    _generationEnvironment.Remove(block.Start, block.Length);
                }
            }

            return generatedFileNames;
        }

        protected virtual void CreateFile(string name, string content)
        {
            var outputPath =  GetOuptPath();
            var fileName = Path.Combine(outputPath, name);
            if (IsFileContentDifferent(fileName, content))
            {
                if (!TemplateHost.OutputPartialClass)
                {
                    File.WriteAllText(fileName, content);
                }
                else
                {
                    string className = name.Substring(0,name.Length-3);
                    string partial = fileName.Replace(TemplateHost.fileExtensionValue, ".gt" + TemplateHost.fileExtensionValue);
                    File.WriteAllText(partial, content);
                    File.WriteAllText(fileName,string.Format( @"namespace {0}
{{
    public partial class {1}
    {{
    }}
}}",TemplateHost.Namespace,className ));
                }
            }
        }
        
        protected bool IsFileContentDifferent(String fileName, string newContent)
        {
            return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent);
        }

        private Block CurrentBlock
        {
            get { return currentBlock; }
            set
            {
                if (CurrentBlock != null)
                {
                    EndBlock();
                }

                if (value != null)
                {
                    value.Start = _generationEnvironment.Length;
                }

                currentBlock = value;
            }
        }

#if !PREPROCESSED_TEMPLATE
        private sealed class VsEntityFrameworkTemplateFileManager : EntityFrameworkTemplateFileManager
        {
            //private EnvDTE.ProjectItem templateProjectItem;
            //private EnvDTE.DTE dte;
            private Action<string> checkOutAction;
            private Action<IEnumerable<string>> projectSyncAction;

            /// <summary>
            /// Creates an instance of the VsEntityFrameworkTemplateFileManager class with the IDynamicHost instance
            /// </summary>
            public VsEntityFrameworkTemplateFileManager(object textTemplating)
                : base(textTemplating)
            {
                var hostServiceProvider = _textTransformation.Host.AsIServiceProvider();
                if (hostServiceProvider == null)
                {
                    throw new ArgumentNullException("Could not obtain hostServiceProvider");
                }

                //dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
                //if (dte == null)
                //{
                //    throw new ArgumentNullException("Could not obtain DTE from host");
                //}

                //templateProjectItem = dte.Solution.FindProjectItem(_textTransformation.Host.TemplateFile);

                //checkOutAction = fileName => dte.SourceControl.CheckOutItem(fileName);
                //projectSyncAction = keepFileNames => ProjectSync(templateProjectItem, keepFileNames);
            }

            public override IEnumerable<string> Process(bool split)
            {
                //if (templateProjectItem.ProjectItems == null)
                //{
                return new List<string>();
                //}

                //var generatedFileNames = base.Process(split);

                //projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null));

                //return generatedFileNames;
            }

            protected override void CreateFile(string fileName, string content)
            {
                if (IsFileContentDifferent(fileName, content))
                {
                    //CheckoutFileIfRequired(fileName);
                    File.WriteAllText(fileName, content);
                }
            }

            //private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, IEnumerable<string> keepFileNames)
            //{
            //    var keepFileNameSet = new HashSet<string>(keepFileNames);
            //    var projectFiles = new Dictionary<string, EnvDTE.ProjectItem>();
            //    var originalOutput = Path.GetFileNameWithoutExtension(templateProjectItem.FileNames[0]);

            //    foreach (EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems)
            //    {
            //        projectFiles.Add(projectItem.FileNames[0], projectItem);
            //    }

            //    // Remove unused items from the project
            //    foreach (var pair in projectFiles)
            //    {
            //        if (!keepFileNames.Contains(pair.Key)
            //            && !(Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalOutput + "."))
            //        {
            //            pair.Value.Delete();
            //        }
            //    }

            //    // Add missing files to the project
            //    foreach (string fileName in keepFileNameSet)
            //    {
            //        if (!projectFiles.ContainsKey(fileName))
            //        {
            //            templateProjectItem.ProjectItems.AddFromFile(fileName);
            //        }
            //    }
            //}

            //private void CheckoutFileIfRequired(string fileName)
            //{
            //    if (dte.SourceControl == null
            //        || !dte.SourceControl.IsItemUnderSCC(fileName)
            //            || dte.SourceControl.IsItemCheckedOut(fileName))
            //    {
            //        return;
            //    }

            //    // run on worker thread to prevent T4 calling back into VS
            //    checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null));
            //}
        }
#endif
    }

    /// <summary>
    /// Responsible creating an instance that can be passed
    /// to helper classes that need to access the TextTransformation
    /// members.  It accesses member by name and signature rather than
    /// by type.  This is necessary when the
    /// template is being used in Preprocessed mode
    /// and there is no common known type that can be
    /// passed instead
    /// </summary>
    public class DynamicTextTransformation
    {
        private dynamic _instance;
        IDynamicHost _dynamicHost;
        
        //private readonly MethodInfo _write;
        //private readonly MethodInfo _writeLine;
        private readonly PropertyInfo _generationEnvironment;
        //private readonly PropertyInfo _errors;
        //private readonly PropertyInfo _host;

        /// <summary>
        /// Creates an instance of the DynamicTextTransformation class around the passed in
        /// TextTransformation shapped instance passed in, or if the passed in instance
        /// already is a DynamicTextTransformation, it casts it and sends it back.
        /// </summary>
        public static DynamicTextTransformation Create(object instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            DynamicTextTransformation textTransformation = instance as DynamicTextTransformation;
            if (textTransformation != null)
            {
                return textTransformation;
            }

            return new DynamicTextTransformation(instance);
        }

        private DynamicTextTransformation(dynamic instance)
        {
            _instance = instance;
            Type type = _instance.GetType();
            //_write = type.GetMethod("Write", new Type[] { typeof(string) });
            //_writeLine = type.GetMethod("WriteLine", new Type[] { typeof(string) });
            _generationEnvironment = type.GetProperty("GenerationEnvironment", BindingFlags.Instance | BindingFlags.NonPublic);
            //_host = type.GetProperty("Host");
            //_errors = type.GetProperty("Errors");
            var host = instance.Host;
        }

        /// <summary>
        /// Gets the value of the wrapped TextTranformation instance's GenerationEnvironment property
        /// </summary>
        public StringBuilder GenerationEnvironment { 
            get { 
                return (StringBuilder)_generationEnvironment.GetValue(_instance, null);
                //return (StringBuilder)_instance.GenerationEnvironment;
            }
        }

        /// <summary>
        /// Gets the value of the wrapped TextTranformation instance's Errors property
        /// </summary>
        public System.CodeDom.Compiler.CompilerErrorCollection Errors { get { return (System.CodeDom.Compiler.CompilerErrorCollection)_instance.Errors; } }

        /// <summary>
        /// Calls the wrapped TextTranformation instance's Write method.
        /// </summary>
        public void Write(string text)
        {
            //_write.Invoke(_instance, new object[] { text });
            _instance.Write(text);
        }

        /// <summary>
        /// Calls the wrapped TextTranformation instance's WriteLine method.
        /// </summary>
        public void WriteLine(string text)
        {
            //_writeLine.Invoke(_instance, new object[] { text });
            _instance.WriteLine(text);
        }

        /// <summary>
        /// Gets the value of the wrapped TextTranformation instance's Host property
        /// if available (shows up when hostspecific is set to true in the template directive) and returns
        /// the appropriate implementation of IDynamicHost
        /// </summary>
        public IDynamicHost Host
        {
            get
            {
                if (_dynamicHost == null)
                {
                    _dynamicHost = new DynamicHost(_instance.Host);
                    
                }
                return _dynamicHost;
            }
        }
    }


    /// <summary>
    /// Reponsible for abstracting the use of Host between times
    /// when it is available and not
    /// </summary>
    public interface IDynamicHost
    {
        /// <summary>
        /// An abstracted call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolveParameterValue
        /// </summary>
        string ResolveParameterValue(string id, string name, string otherName);

        /// <summary>
        /// An abstracted call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolvePath
        /// </summary>
        string ResolvePath(string path);

        /// <summary>
        /// An abstracted call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost TemplateFile
        /// </summary>
        string TemplateFile { get; }

        /// <summary>
        /// Returns the Host instance cast as an IServiceProvider
        /// </summary>
        IServiceProvider AsIServiceProvider();
    }

    /// <summary>
    /// Reponsible for implementing the IDynamicHost as a dynamic
    /// shape wrapper over the Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost interface
    /// rather than type dependent wrapper.  We don't use the
    /// interface type so that the code can be run in preprocessed mode
    /// on a .net framework only installed machine.
    /// </summary>
    public class DynamicHost : IDynamicHost
    {
        private readonly EntityFrameworkTemplateHost _instance;
        private readonly MethodInfo _resolveParameterValue;
        private readonly MethodInfo _resolvePath;
        private readonly PropertyInfo _templateFile;

        public EntityFrameworkTemplateHost TemplateHost { get { return _instance; } }

        /// <summary>
        /// Creates an instance of the DynamicHost class around the passed in
        /// Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost shapped instance passed in.
        /// </summary>
        public DynamicHost(object instance)
        {
            _instance = instance as EntityFrameworkTemplateHost;
            //Type type = _instance.GetType();
            //_resolveParameterValue = type.GetMethod("ResolveParameterValue", new Type[] { typeof(string), typeof(string), typeof(string) });
            //_resolvePath = type.GetMethod("ResolvePath", new Type[] { typeof(string) });
            //_templateFile = type.GetProperty("TemplateFile");

        }

        /// <summary>
        /// A call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolveParameterValue
        /// </summary>
        public string ResolveParameterValue(string id, string name, string otherName)
        {
            //return (string)_resolveParameterValue.Invoke(_instance, new object[] { id, name, otherName });
            return _instance.ResolveParameterValue(id, name, otherName);
        }

        /// <summary>
        /// A call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolvePath
        /// </summary>
        public string ResolvePath(string path)
        {
            //return (string)_resolvePath.Invoke(_instance, new object[] { path });
            return _instance.ResolvePath(path);
        }

        /// <summary>
        /// A call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost TemplateFile
        /// </summary>
        public string TemplateFile
        {
            get
            {
                //return (string)_templateFile.GetValue(_instance, null);
                return _instance.TemplateFile;
            }
        }

        /// <summary>
        /// Returns the Host instance cast as an IServiceProvider
        /// </summary>
        public IServiceProvider AsIServiceProvider()
        {
            //return _instance as IServiceProvider;
            return null;
        }
    }

    /// <summary>
    /// Reponsible for implementing the IDynamicHost when the
    /// Host property is not available on the TextTemplating type. The Host
    /// property only exists when the hostspecific attribute of the template
    /// directive is set to true.
    /// </summary>
    public class NullHost : IDynamicHost
    {
        /// <summary>
        /// An abstraction of the call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolveParameterValue
        /// that simply retuns null.
        /// </summary>
        public string ResolveParameterValue(string id, string name, string otherName)
        {
            return null;
        }

        /// <summary>
        /// An abstraction of the call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost ResolvePath
        /// that simply retuns the path passed in.
        /// </summary>
        public string ResolvePath(string path)
        {
            return path;
        }

        /// <summary>
        /// An abstraction of the call to Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost TemplateFile
        /// that returns null.
        /// </summary>
        public string TemplateFile
        {
            get
            {
                return null;
            }
        }

        /// <summary>
        /// Returns null.
        /// </summary>
        public IServiceProvider AsIServiceProvider()
        {
            return null;
        }
    }

    /// <summary>
    /// Responsible for encapsulating the constants defined in Metadata
    /// </summary>
    public static class MetadataConstants
    {
        public const string EDMX_NAMESPACE_V1 = "http://schemas.microsoft.com/ado/2007/06/edmx";
        public const string EDMX_NAMESPACE_V2 = "http://schemas.microsoft.com/ado/2008/10/edmx";
        public const string EDMX_NAMESPACE_V3 = "http://schemas.microsoft.com/ado/2009/11/edmx";


        public const string CSDL_EXTENSION = ".csdl";
        public const string CSDL_NAMESPACE_V1 = "http://schemas.microsoft.com/ado/2006/04/edm";
        public const string CSDL_NAMESPACE_V2 = "http://schemas.microsoft.com/ado/2008/09/edm";
        public const string CSDL_NAMESPACE_V3 = "http://schemas.microsoft.com/ado/2009/11/edm";
        public const string CSDL_EDMX_SECTION_NAME = "ConceptualModels";
        public const string CSDL_ROOT_ELEMENT_NAME = "Schema";
        public const string EDM_ANNOTATION_09_02 = "http://schemas.microsoft.com/ado/2009/02/edm/annotation";

        public const string SSDL_EXTENSION = ".ssdl";
        public const string SSDL_NAMESPACE_V1 = "http://schemas.microsoft.com/ado/2006/04/edm/ssdl";
        public const string SSDL_NAMESPACE_V2 = "http://schemas.microsoft.com/ado/2009/02/edm/ssdl";
        public const string SSDL_NAMESPACE_V3 = "http://schemas.microsoft.com/ado/2009/11/edm/ssdl";
        public const string SSDL_EDMX_SECTION_NAME = "StorageModels";
        public const string SSDL_ROOT_ELEMENT_NAME = "Schema";

        public const string MSL_EXTENSION = ".msl";
        public const string MSL_NAMESPACE_V1 = "urn:schemas-microsoft-com:windows:storage:mapping:CS";
        public const string MSL_NAMESPACE_V2 = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
        public const string MSL_NAMESPACE_V3 = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
        public const string MSL_EDMX_SECTION_NAME = "Mappings";
        public const string MSL_ROOT_ELEMENT_NAME = "Mapping";
    }

}